package com.litong.modules.face.recognize.service;

import java.io.File;
import java.io.FileInputStream;
import java.io.IOException;
import java.util.ArrayList;
import java.util.Date;
import java.util.List;

import org.apache.commons.codec.digest.DigestUtils;
import org.opencv.core.Mat;
import org.opencv.core.MatOfRect;
import org.opencv.core.Rect;
import org.opencv.core.Scalar;
import org.opencv.imgcodecs.Imgcodecs;
import org.opencv.objdetect.CascadeClassifier;

import com.jfinal.aop.Aop;
import com.jfinal.kit.Kv;
import com.jfinal.plugin.activerecord.Table;
import com.jfinal.plugin.activerecord.TableMapping;
import com.jfinal.upload.UploadFile;
import com.litong.jfinal.service.ActiveRecoredService;
import com.litong.jfinal.service.ApiFormService;
import com.litong.jfinal.thread.pool.ThreadPoolKit;
import com.litong.jfinal.utils.PropKitUtil;
import com.litong.modules.face.recognize.common.model.Employee;
import com.litong.modules.face.recognize.common.model.EmployeePhotoFace;
import com.litong.modules.face.recognize.dataobj.CompareResult;
import com.litong.modules.face.recognize.dataobj.DetectResult;
import com.litong.modules.face.recognize.utils.CascadeClassifierUtils;
import com.litong.modules.face.recognize.utils.FaceUtils;
import com.litong.modules.face.recognize.utils.MatImageUtils;
import com.litong.utils.array.ListUtils;
import com.litong.utils.vo.JsonBean;

import lombok.extern.slf4j.Slf4j;

/**
 * @author bill robot
 * @date 2020年9月6日_下午8:00:32 
 * @version 1.0 
 * @desc
 */
@Slf4j
public class FaceService {
  private ActiveRecoredService ar = Aop.get(ActiveRecoredService.class);
  private ApiFormService apiFormService = Aop.get(ApiFormService.class);

  // 图片存储父路径
  private String savePathFoler = PropKitUtil.get("project.image.face.save.path");

  /**
   * 检测图片中是否包含人脸
   * @param uploadFile
   * @return
   */
  public DetectResult detect(UploadFile uploadFile) {
    long start = System.currentTimeMillis();
    // 获取模型分类器
    CascadeClassifier faceDetector = CascadeClassifierUtils.getFaceClassifier();
    CascadeClassifier eyeDetector = CascadeClassifierUtils.getEyeClassifier();
    // 读取文件为mat,
    Mat mat = Imgcodecs.imread(uploadFile.getFile().getAbsolutePath());
    // 保存检测结果,返回值
    DetectResult detectResult = new DetectResult();
    // 检测结果
    MatOfRect faceRect = new MatOfRect();
    // 保存人脸图片
    List<String> rectImageUris = new ArrayList<>();
    // 检测鼻子
    faceDetector.detectMultiScale(mat, faceRect);
    Rect[] faceRects = faceRect.toArray();
    if (faceRects == null || faceRects.length == 0) {
      log.info("没有检测到人脸:{}", uploadFile.getFile().getAbsolutePath());
      return detectResult;
    }
    detectResult.setContainFace(true);
    detectResult.setFaceCount(faceRects.length);

    // 取出人脸区域,在人脸区域中检测眼睛
    int eyeCount = 0;
    for (Rect rect : faceRects) {
      // 取出mat的矩形区域,好处是在矩形区域内划线会自动同步到子mat
      Mat faceMat = new Mat(mat, rect);
      MatOfRect eyeRect = new MatOfRect();
      eyeDetector.detectMultiScale(faceMat, eyeRect);
      Rect[] eyeRects = eyeRect.toArray();
      if (eyeRects != null && eyeRects.length > 0) {
        eyeCount += eyeRects.length;
        // 画出矩形,保存到图片,添加到list
        for (Rect r : eyeRects) {
          Scalar green = new Scalar(0, 255, 0);
          MatImageUtils.drawRect(faceMat, r, green);
          // rectImageUris.add(MatImageUtils.saveMat(mat));
        }
      }
      // 将人脸部分画出1个矩形
      Scalar red = new Scalar(0, 0, 255);
      MatImageUtils.drawRect(mat, rect, red);
    }

    // 获取文件名称
    String filename = MatImageUtils.getFilename();
    log.info("filename:" + filename);
    // 使用新现场,提高响应速度
    new Thread(() -> {
      MatImageUtils.saveMat(mat, filename);
    }).start();
    rectImageUris.add(filename);
    detectResult.setRectImageUris(ListUtils.toArrayString(rectImageUris));
    if (eyeCount > 0) {
      detectResult.setContainEye(true);
      detectResult.setEyeCount(eyeCount);
    }
    long end = System.currentTimeMillis();
    detectResult.setUseMillisecond(end - start);
    return detectResult;
  }

  /**
   * 比较人脸
   * @param src
   * @param dst
   * @return
   */
  public CompareResult compare(String src, String dst) {
    // 获取模型分类器
    CascadeClassifier faceDetector = CascadeClassifierUtils.getFaceClassifier();
    // 比较人脸
    double compareHist = FaceUtils.compareImage(src, dst, faceDetector);

    if (compareHist > 0.72) {
      return new CompareResult(true, compareHist);
    } else {
      return new CompareResult(false, compareHist);
    }
  }

  /**
   * 保存人脸图片
   * @param file
   * @return
   */
  public JsonBean<DetectResult> uploadImageFace(UploadFile uploadFile) {
    JsonBean<DetectResult> retval = new JsonBean<>();
    File sourceFile = uploadFile.getFile();
    DetectResult detect = detect(uploadFile);
    if (detect.getFaceCount() > 1) {
      return new JsonBean<DetectResult>(-1, "检测到多张人脸", detect);

    }
    if (detect.getFaceCount() < 1) {
      return new JsonBean<DetectResult>(-1, "没有检测到人脸", detect);
    }
    if (detect.getEyeCount() < 2) {
      return new JsonBean<DetectResult>(-1, "没有检测到2个眼睛", detect);
    }
    // 获取图片的md5值
    String md5Hex = null;
    try (FileInputStream fileInputStream = new FileInputStream(sourceFile);) {
      md5Hex = DigestUtils.md5Hex(fileInputStream);
    } catch (IOException e) {
      e.printStackTrace();
    }
    // 判断图片是否重复
    EmployeePhotoFace employeePhotoFace = ar.findFirst("select id", "where md5=?", EmployeePhotoFace.class, md5Hex);
    // 如果不重复保存
    Long id = 0L;
    if (employeePhotoFace == null) {
      // 获取id
      EmployeePhotoFace findFirst = ar.findFirst("SELECT MAX(id)", null, EmployeePhotoFace.class);
      Long maxId = findFirst.getLong("MAX(id)");
      if (maxId == null) {
        id = 1L;
      } else {
        id = maxId + 1;
      }
      // 移动文件并记录数据库
      final String finalmd5Hex = md5Hex;
      final Long finalId = id;
      ThreadPoolKit.use("pool1").execute(() -> {
        renameAndSaveToDb(uploadFile, finalmd5Hex, sourceFile, finalId);
      });
    } else {
      id = employeePhotoFace.getId();
      // 上传旧图片
      sourceFile.delete();
    }
    detect.setId(id);
    retval.setData(detect);
    return retval;
  }

  private void renameAndSaveToDb(UploadFile uploadFile, String md5Hex, File sourceFile, Long id) {
    EmployeePhotoFace employeePhotoFace;
    // 移动文件
    File file = new File(savePathFoler);
    if (!file.exists()) {
      file.mkdirs();
    }
    String fileUri = savePathFoler + "/" + id + "_" + sourceFile.getName();
    File dest = new File(fileUri);
    uploadFile.getFile().renameTo(dest);
    // 记录数据库
    employeePhotoFace = new EmployeePhotoFace();
    employeePhotoFace.setCreateTime(new Date());
    employeePhotoFace.setMd5(md5Hex);
    employeePhotoFace.setId(id);
    employeePhotoFace.setUri(fileUri);
    employeePhotoFace.save();
  }

  /**
   * 根据图片id获取图片的uri
   * @param id
   * @return
   */
  public String getUriByImageId(String id) {
    EmployeePhotoFace employeePhotoFaceDao = new EmployeePhotoFace().dao();
    EmployeePhotoFace findById = employeePhotoFaceDao.findByIdLoadColumns(id, "uri");
    return findById.getUri();
  }

  /**
   * 人脸搜索
   * @param uploadFile
   * @param kv
   * @return
   */
  public JsonBean<Employee> search(File file, Kv kv) {
    Table table = TableMapping.me().getTable(Employee.class);
    StringBuffer where = new StringBuffer();
    List<Object> params = apiFormService.getListWhere(table.getName(), kv, where);
    List<Employee> find = null;
    if (params.size() > 0) {
      find = ar.find("select *", "where " + where.toString(), Employee.class, params.toArray());
    } else {
      find = ar.find("select *", null, Employee.class);
    }
    String src = file.getAbsolutePath();

    long start = System.currentTimeMillis();
    for (Employee employee : find) {
      String uri = getUriByImageId(employee.getPhotoIds());
      log.info("匹配人脸:{}", uri);
      CompareResult compare = compare(src, uri);
      if (compare.isSame()) {
        long end = System.currentTimeMillis();
        log.info("匹配结束,共使用了{}ms", (end - start));
        return new JsonBean<Employee>(employee);
      }
    }
    long end = System.currentTimeMillis();
    log.info("匹配结束,共使用了{}ms", (end - start));
    return new JsonBean<Employee>(-1, "没有搜索匹配的人脸");
  }
}
